package cn.dreampie.client;

import cn.dreampie.client.exception.ClientException;
import cn.dreampie.common.http.ContentType;
import cn.dreampie.common.http.HttpMethod;
import cn.dreampie.common.util.stream.DefaultFileRenamer;
import cn.dreampie.common.util.stream.FileRenamer;
import cn.dreampie.log.Logger;

import javax.net.ssl.*;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.net.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.Random;

/**
 * Created by wangrenhui on 15/1/10.
 */
public class HttpClientConnection {
  private static final Logger logger = Logger.getLogger(HttpClientConnection.class);
  private final SSLSocketFactory sslSocketFactory = initSSLSocketFactory();
  private final TrustAnyHostnameVerifier trustAnyHostnameVerifier = new TrustAnyHostnameVerifier();
  protected HttpClientRequest loginRequest;
  protected ThreadLocal<HttpClientRequest> clientRequestTL = new ThreadLocal<HttpClientRequest>();
  protected CookieManager cookieManager = new CookieManager();
  protected String apiUrl;
  protected FileRenamer renamer = new DefaultFileRenamer();

  protected HttpClientConnection(String apiUrl) {
    this(apiUrl, null);
  }

  protected HttpClientConnection(String apiUrl, HttpClientRequest loginRequest) {
    this(apiUrl, loginRequest, null);
  }

  protected HttpClientConnection(String apiUrl, HttpClientRequest loginRequest, HttpClientRequest httpClientRequest) {
    this.apiUrl = apiUrl;
    this.loginRequest = loginRequest;
    if (httpClientRequest != null) {
      this.clientRequestTL.set(httpClientRequest);
    }else{
      this.clientRequestTL.set(new HttpClientRequest());
    }
    //add cookieManager
    cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
    CookieHandler.setDefault(cookieManager);
  }

  public static String getRandomString(int length) { //length表示生成字符串的长度
    String base = "abcdefghijklmnopqrstuvwxyz0123456789";
    Random random = new Random();
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < length; i++) {
      int number = random.nextInt(base.length());
      sb.append(base.charAt(number));
    }
    return sb.toString();
  }

  /**
   * 文件重命名工具
   *
   * @param renamer 重命名工具
   */
  public void setRenamer(FileRenamer renamer) {
    if (renamer == null) {
      throw new ClientException("FileRenamer must not null.");
    }
    this.renamer = renamer;
  }

  private SSLSocketFactory initSSLSocketFactory() {
    try {
      TrustManager[] tm = {new TrustAnyTrustManager()};
      SSLContext sslContext = SSLContext.getInstance("TLS", "SunJSSE");
      sslContext.init(null, tm, new java.security.SecureRandom());
      return sslContext.getSocketFactory();
    } catch (Exception e) {
      throw new ClientException(e.getMessage(), e);
    }
  }

  /**
   * 获取Connection
   *
   * @param httpMethod
   * @return
   * @throws IOException
   * @throws NoSuchAlgorithmException
   * @throws NoSuchProviderException
   * @throws KeyManagementException
   */
  protected HttpURLConnection getHttpConnection(String httpMethod) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException {
    HttpClientRequest httpClientRequest = clientRequestTL.get();

    URL url = null;
    HttpURLConnection conn = null;

    String contentType = httpClientRequest.getContentType();
    //json请求
    if (contentType.contains(ContentType.JSON.value())) {
      // 除了post意外其他请求类型都用 拼接参数方式
      if (httpMethod.equals(HttpMethod.GET) || httpMethod.equals(HttpMethod.DELETE)) {
        if (!"".equals(httpClientRequest.getJsonParam())) {
          url = new URL(apiUrl + httpClientRequest.getEncodedRestPath() + "?" + httpClientRequest.getEncodedJsonParam());
        } else {
          url = new URL(apiUrl + httpClientRequest.getEncodedRestPath());
        }
        conn = openHttpURLConnection(url, httpClientRequest, httpMethod);
      } else {
        conn = getStreamConnection(httpMethod, httpClientRequest);
        outputParam(conn, httpMethod, httpClientRequest.getJsonParam(), httpClientRequest.getEncoding());
      }
    } else if (contentType.contains(ContentType.MULTIPART.value()) || httpMethod.equals(HttpMethod.POST)) {
      //上传文件类型
      conn = getStreamConnection(httpMethod, httpClientRequest);
      //是上传文件
      Map<String, HttpClientFile> uploadFiles = httpClientRequest.getUploadFiles();
      if (uploadFiles != null && uploadFiles.size() > 0) {
        String boundary = "---------------------------" + getRandomString(13); //boundary就是request头和上传文件内容的分隔符
        conn.setRequestProperty("Content-Type", contentType + "boundary=" + boundary);

        // params
        Map<String, String> params = httpClientRequest.getParams();

        DataOutputStream writer = new DataOutputStream(conn.getOutputStream());
        if (params != null && params.size() > 0) {
          StringBuilder builder = new StringBuilder();

          String value = null;
          for (String key : params.keySet()) {
            value = params.get(key);
            if (value == null) continue;
            builder.append("\r\n").append("--").append(boundary).append("\r\n");
            builder.append("Content-Disposition: form-data; name=\"").append(key).append("\"\r\n\r\n");
            builder.append(value);
          }
          writer.write(builder.toString().getBytes());
        }
        //上传文件
        writeUploadFiles(boundary, uploadFiles, writer);

        byte[] endData = ("\r\n--" + boundary + "--\r\n").getBytes();
        writer.write(endData);
        writer.flush();
        writer.close();
      } else {
        outputParam(conn, httpMethod, httpClientRequest.getEncodedParams(), httpClientRequest.getEncoding());
      }
    } else {
      url = new URL(apiUrl + httpClientRequest.getEncodedUrl());
      conn = openHttpURLConnection(url, httpClientRequest, httpMethod);
    }

    //ssl判断
    if (conn instanceof HttpsURLConnection) {
      ((HttpsURLConnection) conn).setSSLSocketFactory(sslSocketFactory);
      ((HttpsURLConnection) conn).setHostnameVerifier(trustAnyHostnameVerifier);
    }

    conn.setConnectTimeout(httpClientRequest.getConnectTimeOut());
    conn.setReadTimeout(httpClientRequest.getReadTimeOut());
    return conn;
  }

  /**
   * outConnection
   *
   * @param httpMethod
   * @param httpClientRequest
   * @return
   * @throws IOException
   */
  private HttpURLConnection getStreamConnection(String httpMethod, HttpClientRequest httpClientRequest) throws IOException {
    URL url;
    HttpURLConnection conn;
    url = new URL(apiUrl + httpClientRequest.getEncodedRestPath());
    conn = openHttpURLConnection(url, httpClientRequest, httpMethod);

    conn.setDoOutput(true);
    conn.setUseCaches(false);
    return conn;
  }

  /**
   * 输出参数
   *
   * @param conn
   * @param method
   * @param requestParams
   * @throws IOException
   */
  private void outputParam(HttpURLConnection conn, String method, String requestParams, String encoding) throws IOException {
    //写入参数
    if (requestParams != null && !"".equals(requestParams)) {
      DataOutputStream writer = new DataOutputStream(conn.getOutputStream());
      logger.debug("Request out method " + method + ",out parameters " + requestParams);

      writer.write(requestParams.getBytes(encoding));
      writer.flush();
      writer.close();
    }
  }

  /**
   * 写入文件到  服务器
   *
   * @param boundary    分隔符
   * @param uploadFiles 文件集合
   * @param writer      写入对象
   * @throws IOException
   */
  private void writeUploadFiles(String boundary, Map<String, HttpClientFile> uploadFiles, DataOutputStream writer) throws IOException {
    // file
    HttpClientFile httpClientFile;
    for (String key : uploadFiles.keySet()) {
      httpClientFile = uploadFiles.get(key);
      if (httpClientFile == null) continue;

      writer.write(("\r\n" + "--" + boundary + "\r\n" + "Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + httpClientFile.getName() + "\"\r\n" + "Content-Type:" + httpClientFile.getContentType() + "\r\n\r\n").getBytes());

      DataInputStream in = new DataInputStream(httpClientFile.getInputStream());
      int bytes = 0;
      byte[] bufferOut = new byte[1024];
      while ((bytes = in.read(bufferOut)) != -1) {
        writer.write(bufferOut, 0, bytes);
      }
      in.close();
    }
  }

  /**
   * open a  Connection
   *
   * @param url    url
   * @param method method
   * @return
   * @throws IOException
   */
  private HttpURLConnection openHttpURLConnection(URL url, HttpClientRequest httpClientRequest, String method) throws IOException {
    logger.info("Open connection for api " + url.getPath());

    HttpURLConnection.setFollowRedirects(true);
    HttpURLConnection conn;
    conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod(method);

    String downloadFile = httpClientRequest.getDownloadFile();
    if (downloadFile != null) {
      File file = new File(downloadFile);
      if (file.exists()) {
        //设置下载区间
        conn.setRequestProperty("RANGE", "bytes=" + file.length() + "-");
      }
    }
    Map<String, String> headers = httpClientRequest.getHeaders();
    if (headers != null && !headers.isEmpty()) {
      for (Map.Entry<String, String> entry : headers.entrySet()) {
        conn.setRequestProperty(entry.getKey(), entry.getValue());
      }
    }
    logger.info("Hold cookie: %s.", cookieManager.getCookieStore().getCookies());
    conn.setRequestProperty("Content-Type", httpClientRequest.getContentType());
    conn.setRequestProperty("User-Agent", httpClientRequest.getUserAgent());
    return conn;
  }

  /**
   * https 域名校验
   */
  private class TrustAnyHostnameVerifier implements HostnameVerifier {
    public boolean verify(String hostname, SSLSession session) {
      return true;
    }
  }

  /**
   * https 证书管理
   */
  private class TrustAnyTrustManager implements X509TrustManager {
    public X509Certificate[] getAcceptedIssuers() {
      return null;
    }

    public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
    }

    public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
    }
  }


}
